#include <stdio.h>
#include <jpeglib.h>
#include <setjmp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>
#include <stdlib.h>


#define FB_DEVICE_NAME "/dev/fb0"
int fd_fb;
struct fb_var_screeninfo var;	/* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;

typedef struct PictureData {
	FILE *ptFp;//文件句柄
	int iBpp;
	int iRgbSize;
	
	int iWidth;
	int iHeight;
	unsigned char *pucRgbData;

		/* use for JPG */
	struct jpeg_decompress_struct tInfo;
	struct jpeg_error_mgr tJerr;
}T_PictureData, *PT_PictureData;

/**********************************************************************
 * 函数名称： IsJpg
 * 功能描述：判断是否为Jpg文件
 * 输入参数： ptData - 内含图像信息
 					strFileName - 文件名
 * 返 回 值：0 - 不是JPG格式 其他-是JPG格式
 ***********************************************************************/
static int IsJpg(PT_PictureData ptData, const char *strFileName) 
{
	int iRet;

	jpeg_stdio_src(&ptData->tInfo, ptData->ptFp);

	/* 用jpeg_read_header获得jpeg信息*/
	iRet = jpeg_read_header(&ptData->tInfo, TRUE);
	
    	return (iRet == JPEG_HEADER_OK);
}

/**********************************************************************
 * 函数名称： DecodeJpg2Rgb
 * 功能描述：把JPG文件解析为RGB888格式
 * 输入参数： ptData - 内含文件信息
 *                             strFileName - 文件名
 * 输出参数：PT_PictureData->pucRgbData - 内含rgb数据
 * 返 回 值：0 - 成功 其他-失败
 ***********************************************************************/
static int DecodeJpg2Rgb(const char *strFileName, PT_PictureData ptData){
	int iRowSize;
	unsigned char *pucbuffer;
	unsigned char *pucHelp;//辅助拷贝变量
	
	/* 1.分配和初始化一个jpeg_compress_struct结构体 */
	ptData->tInfo.err = jpeg_std_error(&ptData->tJerr);
	jpeg_create_decompress(&ptData->tInfo);


	/* 2.指定源文件*/
	if ((ptData->ptFp= fopen(strFileName, "rb")) == NULL) {
		fprintf(stderr, "can't open %s\n", strFileName);
		return -1;
	}

	/* 3.获得jpg信息头并设置解压参数并判断是否为JPEG格式文件 */
	if (!IsJpg(ptData, strFileName)) {
	    printf("file is not jpg ...\n");
	    return -1;
	}

	/* 默认尺寸为原尺寸 */
	ptData->tInfo.scale_num = 1;
	ptData->tInfo.scale_denom = 1;
	/* 4. 启动解压：jpeg_start_decompress */	
	jpeg_start_decompress(&ptData->tInfo);

	/* 解压完成后可以通过tInfo中的成员获得图像的某些信息 */
	ptData->iWidth= ptData->tInfo.output_width;
	ptData->iHeight = ptData->tInfo.output_height;
	ptData->iBpp = ptData->tInfo.output_components*8;
	/* 计算一行的数据长度 */ 
	iRowSize = ptData->iWidth * ptData->tInfo.output_components;
	pucbuffer = malloc(iRowSize);
	ptData->iRgbSize= iRowSize * ptData->iHeight;
	ptData->pucRgbData = malloc(ptData->iRgbSize);

	/* pucHelp指向ptData->pucRgbData首地址 */
	pucHelp = ptData->pucRgbData;
	/*  5.循环调用jpeg_read_scanlines来一行一行地获得解压的数据 */
	while (ptData->tInfo.output_scanline < ptData->tInfo.output_height) 
	{
		/* 调用jpeg_read_scanlines得到的时候会存到pucbuffer中 */
		jpeg_read_scanlines(&ptData->tInfo, &pucbuffer, 1);
		/* 将数据一行行读到缓冲区中 */
		memcpy(pucHelp,pucbuffer,iRowSize);
		pucHelp  += iRowSize;
	}
	free(pucbuffer);
	/* 6.完成读取 */
	jpeg_finish_decompress(&ptData->tInfo);
	/* 7.释放jpeg_compress_struct结构体 */
	jpeg_destroy_decompress(&ptData->tInfo);

	return 0;
}


/**********************************************************************
 * 函数名称： ReleaseJpg2Rgb
 * 功能描述：释放解析及使用JPG时使用的内存
 * 输入参数： ptPicData - 内含文件信息
 * 返 回 值：无
 ***********************************************************************/
void ReleaseJpg2Rgb(PT_PictureData ptData){
	free(ptData->pucRgbData);
}

/**********************************************************************
 * 函数名称： FBDeviceInit
 * 功能描述： "framebuffer显示设备"的初始化函数
 * 输入参数： 无
 * 输出参数： 无
 * 返 回 值： 0 - 成功, 其他值 - 失败
 ***********************************************************************/
static int FBDeviceInit(void)
{
	int ret;
	
	fd_fb = open(FB_DEVICE_NAME, O_RDWR);
	if (0 > fd_fb)
	{
		printf("can't open %s\n", FB_DEVICE_NAME);
	}

	ret = ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);
	if (ret < 0)
	{
		printf("can't get fb's var\n");
		return -1;
	}

	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;  //整屏全部像素点所占的字节数：800*480*32/8=1536000Byte = 1500KB ≈ 1.46MB
	fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fbmem == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

    line_width  = var.xres * var.bits_per_pixel / 8;  //每一行像素点所占据的字节数 800*32/8bit = 
	pixel_width = var.bits_per_pixel / 8;  //一个像素点的宽度 32/8bit = 4Byte
	
	return 0;
}

/**********************************************************************
 * 函数名称： lcd_put_pixel
 * 功能描述： 在LCD指定位置上输出指定颜色（描点）
 * 输入参数： x坐标，y坐标，颜色
 * 输出参数： 无
 * 返 回 值： 会
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
	unsigned int *pen_32 = (unsigned int *)(fbmem+y*line_width+x*pixel_width);
	*pen_32 = color;
}

/**********************************************************************
 * 函数名称： FbShowPicture
 * 功能描述：将转换后的rgb数据显示到LCD上
 * 输入参数： ptData - 内含图像信息
 * 返 回 值： 无
 ***********************************************************************/
static void FbShowPicture(PT_PictureData ptData){
	int i ,j;
	int iColor;
	for(i = 0; i<ptData->iHeight; i++){
		for(j = 0; j<ptData->iWidth*3; j+=3){
			if(j/3<var.xres && i<var.yres){
				iColor = (ptData->pucRgbData[i*ptData->iWidth*3+j+0]<<16) + (ptData->pucRgbData[i*ptData->iWidth*3+j+1]<<8) + (ptData->pucRgbData[i*ptData->iWidth*3+j+2]<<0);
				lcd_put_pixel(j/3, i, iColor);
			}
		}
	}
}

int main(int argc,char *argv[])
{
    T_PictureData ptData;
    int ret;

    ret = FBDeviceInit();
    printf("ret = %d\n", ret);

    ret = DecodeJpg2Rgb(argv[1], &ptData);
    printf("ret = %d\n", ret);

    FbShowPicture(&ptData);

    ReleaseJpg2Rgb(&ptData);

    return 0;
}